#!/usr/bin/env python
# -*- coding: utf-8 -*-

# #portugues@ScoutLink 2006 JOTI Bot (slbot)
# Copyright (c) 2006, ScoutLink #portugues team
# This source is released under the New BSD License. See the LICENSE file for further
# information.

# $Author: acoster $
# $Date: 2006-10-02 23:31:57 +0000 (Mon, 02 Oct 2006) $ 

__version__ = "$Revision: 24 $"
# $Source$

import commands
import sys
import os

import twisted.words.protocols.irc
import twisted.internet.task
import twisted.plugin

import slauth
import sllog
import ircchan
import slinterface

#if __name__ == "__main__":
#    print "You should not run this file ;)"
#    sys.exit()

def __break_user__(str):
    """Splits the user prefix in 3 parts:
         0: nickname
         1: ident
         2: hostname"""
    tmp  = str.split('!')
    tmp2 = tmp[1].split('@')
    return [tmp[0]] + tmp2

class SLBot(twisted.words.protocols.irc.IRCClient):
    # channel dictionary
    channels = {}

    # plugin callable objects
    __pl_action  = [] # user (prefix), chan, msg
    __pl_join    = [] # user, chan
    __pl_kick    = [] # kickee, chan, kicker, msg
    __pl_mode    = [] # user, chan, set, mode, args
    __pl_notice  = [] # user (prefix), target, msg
    __pl_part    = [] # user, chan
    __pl_privmsg = [] # user (prefix), msg
    __pl_public  = [] # user, chan, msg
    __pl_rename  = [] # oldnick, newnick
    __pl_signon  = [] # when bot signs on
    __pl_topic   = [] # user, channel, newTopic
    __pl_quit    = [] # user, msg

    # commands dictionary
    __command_list = {}
    __command_list_strs = []

    # supported plugin types
    __supported_plugins = ['action', 'join', 'kick', 'mode', 'notice', 'part', 'privmsg',
    'public', 'quit', 'rename', 'signon', 'topic']

    def __getattr(self, str):
        """A simple (yet ugly) hack to emulate getattr as a "friend" function of this class."""

        return getattr(self, "_%s__%s" % ('SLBot', str))

    def __init__(self, cfg):
        """Class constructor. The parameter cfg is a ConfigParser object, containing
        the parse results for the ini file.
        
        """
        self.__log = sllog.SLLog(cfg.get('logging','stdout'), cfg.get('logging','stderr'))
        self.log   = self.__log.log
        self.error = self.__log.error
        self.log('session started')

        self.realname = cfg.get('main', 'realname')
        self.nickname = cfg.get('main', 'nickname')
        self.username = cfg.get('main', 'username')

        self.auth = slauth.SLAuth("%s.userlist" % (self.nickname,))
        self.versionEnv  = commands.getoutput('uname -nsrm')
        self.versionName = 'slbot.py'
        self.versionNum  = '0.1-svn'

        # past this point, no plugins/commands will be added anymore,
        # except if some idiot add it by hand thru the telnet console.
        self.cfg = cfg
        self.__get_plugins()
        self.reg_command('help', self.cmd_help)

        # caches command list for help ;-)
        chaves = self.__command_list.keys()
        chaves.sort()

        n = len(chaves)/5 + (len(chaves)%5 != 0)
        for i in range(n):
            str = chaves[i * 5:(i+1) * 5].__str__().replace('[', "\002").replace(']', "\002").replace(',','')
            self.__command_list_strs.append(str.replace("'", ''))

    # plugin handlers
    def __get_plugins(self):
        """Iterates over the plugin directory, and calls the register callback for each
        plugin it finds. Should be done only once.
        
        """
        for i in twisted.plugin.getPlugIns(slinterface.ISLPlugin):
            i.register_me(self)

    def unreg_plugin(self, type, obj):
        """Function that is called by each plugin in order to unregister itself"""
        if type not in self.__supported_plugins:
            raise Exception("Invalid plugin type: %s" % (type,))
        self.__getattr("pl_%s" % (type,)).remove(obj)

    def reg_plugin(self, type, obj):
        """Function that is called by each plugin."""
        if type not in self.__supported_plugins:
            raise Exception("Invalid plugin type: %s" % (type,))
        self.log('Loading plugin %s implemented by %s' % 
          (type, os.path.basename(sys.modules[obj.__module__].__file__)))
        self.__getattr("pl_%s" % (type,)).append(obj)

    def call_plugins(self, type, *args):
        if type not in self.__supported_plugins:
            raise Exception("Invalid plugin type: %s" % (type,))

        for i in self.__getattr("pl_%s" % (type,)):
            getattr(i, type)(*args)

    # command handlers
    def reg_command(self, cmd, fn):
        if self.__command_list.has_key(cmd.lower()):
            raise Exception("Command already exists: %s." % (cmd.lower(),))
        self.__command_list[cmd.lower()] = fn

    def run_command(self, user, cmd, args):
        if self.__command_list.has_key(cmd):
            self.__command_list[cmd](user, args)
        else:
            self.notice(__break_user__(user)[0], "Invalid command: \002%s\002" % (cmd))

    # built-in commands
    def cmd_help(self, user, args):
        """Lists all available commands, or gives help on a particular command
        Usage: \002.help <command name>\002"""
        u = __break_user__(user)

        if args == '':
            self.notice(u[0], 'These are the available commands:')
            for i in self.__command_list_strs:
                self.notice(u[0], i)
        else:
            cmd = args.split(' ')[0].lower()
            if self.__command_list[cmd].__doc__ == None:
                self.notice(u[0], "No help available for \002%s\002." % (cmd,))
            else:
                doc = self.__command_list[cmd].__doc__.splitlines()
                self.notice(u[0], 'Help for the \002%s\002 command:' % (cmd,))
                for i in doc:
                    self.notice(u[0], "%s" % (i.strip(),))

    # one might call this "perform", like in mIRC
    def signedOn(self):
        self.timer = twisted.internet.task.LoopingCall(self.sendLine, 
                "PING %s" % self.cfg.get('main', 'server'))
        self.timer.start(30)
        self.call_plugins("signon")

    # actions involving me
    def joined(self, channel):
        self.channels[channel] = ircchan.IRCChan(channel)

    def left(self, channel):
        self.channels.remove(channel)

    # incoming messages - user is the entire user prefix
    def privmsg(self, user, chan, msg):
        if chan[0] == '#':
            self.call_plugins("public", user, chan, msg)
            self.channels[chan].updateLastSent(user)
        elif msg[0] == '.':
            msg = msg.strip()
            parts = msg[1:].split(' ', 1)
            if len(parts) == 1:
                self.run_command(user, parts[0].lower(), '')
            else:
                self.run_command(user, parts[0].lower(), parts[1].strip())
        else:
            self.call_plugins("privmsg", user, msg)

    def noticed(self, user, chan, msg):
        self.call_plugins("notice", user, chan, msg)

    def action(self, user, chan, msg):
        if chan[0] == '#':
            self.call_plugins("action", user, chan, msg)
            self.channels[chan].updateLastSent(user)

    # user left/joined/kicked/quited/changed the nick...
    # those only give the user's nick
    def userJoined(self, user, channel):
        self.channels[channel].addUser(user)
        self.call_plugins("join", user, channel)

    def userKicked(self, kickee, channel, kicker, reason):
        self.channels[channel].remUser(kicked)
        self.call_plugins("kick", kickee, channel, kicker, reason)

    def userLeft(self, user, channel):
        self.channels[channel].remUser(user)
        self.call_plugins("part", user, channel)

    def userQuit(self, user, message):
        #TODO: fix that
        self.call_plugins("quit", user, message)

    def userRenamed(self, oldNick, newNick):
        #TODO: fix that
        self.call_plugins("rename", oldNick, newNick)

    # channel changes
    def topicUpdated(self, user, channel, newTopic):
        self.call_plugins("topic", user, channel, newTopic)

    def modeChanged(self, user, channel, set, modes, args):
        self.call_plugins("mode", user, channel, set, modes, args)
        self.log("%s sets mode %s %s" % (user, modes, args))

    # server->client messages redefined (low level)
    def irc_RPL_NAMREPLY(self, prefix, params):
        self.channels[params[2]].users = params[3].split()
